import { relationalStore } from '@kit.ArkData';
import { Context } from '@ohos.arkui.UIContext';
import { Migration } from '../../../../Index';
import { RdbStoreWrapper } from '../common/RdbStoreWrapper';

/**
 * 抽象 Database 基类
 */
export abstract class Database {
  private _rdbStore: relationalStore.RdbStore = null

  private readonly version: number

  /**
   * 构建 Database 实例
   * @param version Database 最新版本号
   */
  constructor(version: number) {
    this.version = version
  }

  /**
   * 获取原始的 RdbStore 实例
   * 在未初始化时调用返回 null
   *
   * @returns 已初始化的 RdbStore 实例
   */
  get rdbStore(): relationalStore.RdbStore {
    return this._rdbStore
  }

  /**
   * 实现该函数并返回 Database 的 RdbStore 实例
   * 该函数将在外部调用 init 函数进行初始化时调用
   *
   * @param context 应用上下文
   * @returns RdbStore 实例
   */
  protected abstract initDb(context: Context): Promise<relationalStore.RdbStore>

  /**
   * 用于初始化 Database
   * 仅可调用一次
   *
   * @param context 应用上下文
   */
  async init(context: Context) {
    this._rdbStore = await this.initDb(context)
    if (this._rdbStore.version > this.version) {
      throw new Error('The latest version of the local database is greater than the version number set by the database')
    }
    const rdbStore = new RdbStoreWrapper(this._rdbStore)
    // 判断是否需要初始化 Database
    if (this._rdbStore.version === 0) {
      // 直接初始化至最新版本
      this.onDatabaseCreate(rdbStore)
      this._rdbStore.version = this.version
    } else if (this._rdbStore.version < this.version) {
      const migrationTasks = Migration
        .ownFunctions(this)
        .map(item => ({
          config: Migration.get(item)!!,
          value: item
        }))
        .sort((a, b) => a.config.startVersion - b.config.startVersion)
      // 开始验证
      if (migrationTasks.length === 0) {
        throw new Error('No database migrations defined')
      }
      // 解构首个和末个配置项
      const firstConfig = migrationTasks[0].config
      const lastConfig = migrationTasks[migrationTasks.length - 1].config

      // 验证初始版本一致性
      if (firstConfig.startVersion !== 1) {
        throw new Error(`Initial migration version span invalid: ${firstConfig.startVersion} → ${firstConfig.endVersion}`)
      }
      // 验证最终版本一致性
      if (lastConfig.endVersion !== this.version) {
        throw new Error(`Final migration target mismatch: Expected ${this.version}, found ${lastConfig.endVersion}`)
      }

      // 顺序验证（从第二项开始）
      for (let i = 1; i < migrationTasks.length; i++) {
        const prev = migrationTasks[i - 1].config
        const curr = migrationTasks[i].config
        const versionGap = curr.startVersion - prev.endVersion

        if (versionGap !== 0) {
          throw new Error(`Version sequence break between migrations #${migrationTasks[i-
            1].value.name} and #${migrationTasks[i].value.name}: ` +
            `${prev.endVersion} → ${curr.startVersion}`)
        }
      }

      // 验证完成，开始迁移
      for await (const migration of migrationTasks) {
        await this.launchTransaction(() => migration.value.call(this, rdbStore))
          .then(() => {
            this.rdbStore.version = migration.config.endVersion
          })
          .catch(e => {
            throw e
          })
      }
    }
    this.onCreate(rdbStore)
    delete this.init
  }

  /**
   * 关闭 Database
   */
  close() {
    this._rdbStore
      .close()
      .then(() => {
        this.onClose()
      })
  }

  /**
   * Database 准备就绪时回调
   *
   * @param rdbStore 辅助访问 Database
   */
  protected abstract onCreate(rdbStore: RdbStoreWrapper): void

  /**
   * 在 Database 初始化时版本号为 0 时回调
   * 初始化成功后版本号会设置为 `this.version`
   *
   * @param rdbStore 辅助访问 Database
   */
  protected abstract onDatabaseCreate(rdbStore: RdbStoreWrapper): void

  /**
   * Database 关闭完成回调
   */
  protected onClose(): void {
  }

  /**
   * 执行全局事务操作，失败自动回退
   *
   * @param scope 事务处理函数，接收当前 Database 实例作为参数
   * @returns Promise
   */
  async launchTransaction<T>(scope: (it: this) => T): Promise<T> {
    return new Promise((resolve, reject) => {
      try {
        this._rdbStore.beginTransaction()
        const result = scope(this)
        this._rdbStore.commit()
        resolve(result)
      } catch (e) {
        this._rdbStore.rollBack()
        reject(e)
      }
    })
  }
}